/*----------------------------------------------------------------------*
 *	CLASS:		Qt3State
 *	Quantum Tic Tac Toe
 *	Copyright (c) 2004-2009 Novatia Labs, Inc.  All rights reserved.
 *----------------------------------------------------------------------*/

package com.novatialabs.qttt.engine.impl;

import java.util.ArrayList;
import java.util.List;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.novatialabs.qttt.engine.Qt3Game;
import com.novatialabs.qttt.engine.Qt3Player;
import com.novatialabs.qttt.engine.Qt3Square;
import com.novatialabs.qttt.engine.Qt3Game.Score;

/**
 * State provides the evaluated status of the game at a particular step.
 *
 * @Author	John Levine
 * @Author	Duane Gibson
 * @Author Friendly development resources cheerfully provided by Charles Phares.
 * @Author	$Author$
 * @Version	$Revision$
 */

/*package*/ final class Qt3State
{

    private Qt3SquareImpl[]	boardState;
    private Qt3Tangle[]		tangleState;
    private Qt3ClassicalBoardImpl[]	equivalentClassicalGames;

    private boolean	gameOver;
    private Qt3Player	winner;
    private Score		winnerScore;
    private Score		loserScore;

    private Qt3Tangle	moveTangle;

    private static final int	MAX_TANGLES	= 4;

    private static Logger log = Logger.getLogger(Qt3State.class.getName());

   /*-------------------------------------------------------------------*
    *	CONSTRUCTOR:	Qt3State()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 22:24:24 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Constructs a new, initial Qt3State.
    */

    /*package*/ Qt3State()
    {

	Qt3SquareImpl[] board = new Qt3SquareImpl[Qt3Constants.MAX_SQUARES];
	for ( int index = 0 ; index < Qt3Constants.MAX_SQUARES ; ++index ) {
	    board[index] = new Qt3SquareImpl();
	}
	this.boardState = board;

	Qt3Tangle[] knot = new Qt3Tangle[MAX_TANGLES];
	// for ( int index = 0 ; index < MAX_TANGLES ; ++index ) {
	//     knot[index] = null;
	// }
	this.tangleState = knot;

    } // Qt3State()

   /*-------------------------------------------------------------------*
    *	CONSTRUCTOR:	Qt3State()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan 10 22:18:59 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Copy constructor.
    * Copies the base data to which the state change will be applied.
    * 
    * @param sourceState	the state to copy.
    */

    private Qt3State(
       	Qt3State	sourceState
	)
    {
//	final Qt3SquareImpl[] srcBoard = sourceState.boardState;

	Qt3SquareImpl[] board = new Qt3SquareImpl[Qt3Constants.MAX_SQUARES];
	for ( int index = 0 ; index < Qt3Constants.MAX_SQUARES ; ++index ) {
	    board[index] = sourceState.boardState[index].cloneSquare();
	}
	this.boardState = board;

	Qt3Tangle[] knot = new Qt3Tangle[MAX_TANGLES];
	for ( int index = 0 ; index < MAX_TANGLES ; ++index ) {
	    knot[index] = sourceState.tangleState[index];
	}
	this.tangleState = knot;

    } // Qt3State()

   /*-------------------------------------------------------------------*
    *	CONSTRUCTOR:	Qt3State()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Jan  8 21:21:00 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Constructs state from a copy of the given state with the specified
    * move applied.
    * 
    * @param sourceState	the state to copy.
    * @param moveNumber		number [1-9] of the move being applied. 
    * @param firstSquare	first square of move. 
    * @param secondSquare	second square of move. 
    */

    /*package*/ Qt3State(
       	Qt3State	sourceState,
	final int	moveNumber,
       	final int	firstSquareIndex,
	final int	secondSquareIndex
	)
    {
	this(sourceState);

	final Qt3SquareImpl[] board = this.boardState;

	// 
	// Add the move to the two squares.
	//
	final Qt3SquareImpl firstSq = board[firstSquareIndex];
	final Qt3SquareImpl secondSq = board[secondSquareIndex];

	firstSq.addMove(moveNumber);
	secondSq.addMove(moveNumber);

	//
	// Combine the squares in a tangle.
	//
	Qt3Tangle tangle =
		new Qt3Tangle(moveNumber, firstSquareIndex, secondSquareIndex,
			this.boardState, this.tangleState);
	this.moveTangle = tangle;

	//
	// Update the classical boards. 
	//
	List<Qt3ClassicalBoardImpl> updatedClassicalBoards;
	updatedClassicalBoards = new ArrayList<Qt3ClassicalBoardImpl>();
	final Qt3ClassicalBoardImpl[] sourceCBArray =
				sourceState.equivalentClassicalGames;
	final Qt3Player currentPlayer =
				Qt3GameEngine.getPlayerForMove(moveNumber);

	if ( sourceCBArray == null ) {

	    Qt3ClassicalBoardImpl cb;

	    cb = new Qt3ClassicalBoardImpl();
	    cb.addMove(moveNumber, currentPlayer, firstSquareIndex);
	    updatedClassicalBoards.add(cb);

	    cb = new Qt3ClassicalBoardImpl();
	    cb.addMove(moveNumber, currentPlayer, secondSquareIndex);
	    updatedClassicalBoards.add(cb);

	} else {

	    final boolean filterTangleStems = this.moveTangle.isCollapsible();

	    for ( int cbIndex = 0 ; cbIndex < sourceCBArray.length ;
		    ++cbIndex ) {
		Qt3ClassicalBoardImpl cb = sourceCBArray[cbIndex];

		if ( filterTangleStems && this.boardStateContradicts(cb) ) {
		    continue;	// Skip this board, it is already
				// contradicted by the stems in the
				// impending entanglement.
		}

		if ( cb.getPlayerAtSquareIndex(firstSquareIndex) == null ) {
		    updatedClassicalBoards.add(
			    cb.cloneAndAddMove(
				moveNumber, currentPlayer, firstSquareIndex));
		}

		if ( (firstSquareIndex != secondSquareIndex)
			&& (cb.getPlayerAtSquareIndex(secondSquareIndex)
				== null) ) {
		    updatedClassicalBoards.add(
			    cb.cloneAndAddMove(
				moveNumber, currentPlayer, secondSquareIndex));
		}

	    }
	}

	this.equivalentClassicalGames = (Qt3ClassicalBoardImpl[])
	    updatedClassicalBoards.toArray(
		new Qt3ClassicalBoardImpl[updatedClassicalBoards.size()]);

	//
	// Update end-of-game state.
	//
	this.evaluateForEndOfGame();

    } // Qt3State()

   /*-------------------------------------------------------------------*
    *	CONSTRUCTOR:	Qt3State()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Sat Jan  8 21:21:35 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Constructs a copy of the given state with the specified resolution.
    * 
    * @param sourceState	the state to copy.
    * @param resolveToFirstSquareOfMove	resolve to first square of move. 
    */

    /*package*/ Qt3State(
       	Qt3State	sourceState,
       	boolean		resolveToFirstSquareOfMove
	)
    {
	this(sourceState);

	final Qt3SquareImpl[] board = this.boardState;

	//
	// Apply the tangle resolution to the affected squares.
	//

	final Qt3Tangle resolvedTangle = sourceState.getTangle();
	resolvedTangle.resolve(resolveToFirstSquareOfMove, board);
	this.tangleState[resolvedTangle.getTangleIndex()] = null;

	//
	// And finalize the stems.
	//
	for ( int index = 0 ; index < Qt3Constants.MAX_SQUARES ; ++index ) {
	    board[index].finalizeCollapse();
	}

	//
	// Update the classical boards. 
	//
	List<Qt3ClassicalBoardImpl> updatedClassicalBoards;
	updatedClassicalBoards = new ArrayList<Qt3ClassicalBoardImpl>();
	final Qt3ClassicalBoardImpl[] sourceCBArray =
				sourceState.equivalentClassicalGames;

	for ( int cbIndex = 0 ; cbIndex < sourceCBArray.length ; ++cbIndex ) {
	    Qt3ClassicalBoardImpl cb = sourceCBArray[cbIndex];

	    if ( ! this.boardStateContradicts(cb) ) {
		updatedClassicalBoards.add(cb);
	    }

	}

	this.equivalentClassicalGames = (Qt3ClassicalBoardImpl[])
	    updatedClassicalBoards.toArray(
		new Qt3ClassicalBoardImpl[updatedClassicalBoards.size()]);

	//
	// Update end-of-game state.
	//
	this.evaluateForEndOfGame();

    } // Qt3State()

   /*-------------------------------------------------------------------*
    *	METHOD:		evaluateForEndOfGame()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Tue Jan 11 08:44:01 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Evaluate the game state to determine whether there is a win
    * condition.  If we find a win condition, finalize the game.
    *
    * <p> 
    * Algorithm:
    * Remember the board square indexes:<br>
    *  0 | 1 | 2  <br>
    * ---+---+--- <br>
    *  3 | 4 | 5  <br>
    * ---+---+--- <br>
    *  6 | 7 | 8  <br>
    * We check the center square (4) first since 4 out of the 8 line-ups
    * use this square.  If there is a permanent mark in that square, we
    * check for one of the 4 possible line-ups.  If there is no win
    * condition in the center square, we check a corner (0) which is used
    * by 2 of the remaining 4 line-ups.  Then we check for the last 2
    * line-ups in square 8.
    *
    * If there is a win condition, we look for a competing win condition
    * in any remaining open rows and/or a complementary win condition.
    */

    private void evaluateForEndOfGame()
    {
	final Qt3Square[] board = this.boardState;

	// Cache the owners (X, O, or null) of each square
	// in local variables for easy access.
	final Qt3Player s0 = board[0].getOwner();
	final Qt3Player s1 = board[1].getOwner();
	final Qt3Player s2 = board[2].getOwner();
	final Qt3Player s3 = board[3].getOwner();
	final Qt3Player s4 = board[4].getOwner();
	final Qt3Player s5 = board[5].getOwner();
	final Qt3Player s6 = board[6].getOwner();
	final Qt3Player s7 = board[7].getOwner();
	final Qt3Player s8 = board[8].getOwner();

	if ( s4 != null ) {
	    if ( (s0 == s4) && (s8 == s4) ) {
		// We found a diagonal win.
		this.winner = s4;
		this.gameOver = true;
		// There can be no competing line-ups, so just check
		// for a complimentary win.
		if ( (s2 == s4) && (s6 == s4) ) {
		    // Score the "big X" line-up.
		    this.winnerScore = this.score(0,8,4,2,6);
		} else if ( (s1 == s4) && (s7 == s4) ) {
		    this.winnerScore = this.score(0,8,4,1,7);
		} else if ( (s3 == s4) && (s5 == s4) ) {
		    this.winnerScore = this.score(0,8,4,3,5);
		} else if ( (s1 == s4) && (s2 == s4) ) {
		    this.winnerScore = this.score(8,4,0,1,2);
		} else if ( (s3 == s4) && (s6 == s4) ) {
		    this.winnerScore = this.score(8,4,0,3,6);
		} else if ( (s2 == s4) && (s5 == s4) ) {
		    this.winnerScore = this.score(4,0,8,5,2);
		} else if ( (s6 == s4) && (s7 == s4) ) {
		    this.winnerScore = this.score(4,0,8,7,6);
		} else {
		    this.winnerScore = Qt3Game.Score.FULL_POINT;	// Score a single line-up.
		}
		return;
	    }
	   
	    if ( (s2 == s4) && (s6 == s4) ) {
		// The other diagonal win.
		this.winner = s4;
		this.gameOver = true;
		// There can be no competing line-ups, so just check
		// for a complimentary win.
		// Note: we know 0-4-8 did not match already.
		if ( (s1 == s4) && (s7 == s4) ) {
		    this.winnerScore = this.score(2,6,4,1,7);
		} else if ( (s3 == s4) && (s5 == s4) ) {
		    this.winnerScore = this.score(2,6,4,3,5);
		} else if ( (s0 == s4) && (s1 == s4) ) {
		    this.winnerScore = this.score(6,4,2,1,0);
		} else if ( (s5 == s4) && (s8 == s4) ) {
		    this.winnerScore = this.score(6,4,2,5,8);
		} else if ( (s0 == s4) && (s3 == s4) ) {
		    this.winnerScore = this.score(2,4,6,3,0);
		} else if ( (s7 == s4) && (s8 == s4) ) {
		    this.winnerScore = this.score(2,4,6,7,8);
		} else {
		    this.winnerScore = Qt3Game.Score.FULL_POINT;	// Score a single line-up.
		}
		return;
	    }

	    if ( (s1 == s4) && (s7 == s4) ) {
		// There is a line-up in 1-4-7. 
		this.gameOver = true;
		// Check for competing line-ups.
		if ( (s0 != null) && (s0 != s4) && (s3 == s0) && (s6 == s0) ) {
		    // There is a competing line-up in 0-3-6.
		    this.winner = this.scoreCompetingLineups(1,4,7, 0,3,6);
		    this.winnerScore = Qt3Game.Score.FULL_POINT;
		    this.loserScore = Qt3Game.Score.HALF_POINT;
		} else if ( (s2 != null) && (s2 != s4)
		       	&& (s5 == s2) && (s8 == s2) ) {
		    // There is a competing line-up in 2-5-8.
		    this.winner = this.scoreCompetingLineups(1,4,7, 2,5,8);
		    this.winnerScore = Qt3Game.Score.FULL_POINT;
		    this.loserScore = Qt3Game.Score.HALF_POINT;
		} else {
		    this.winner = s4;
		    // Check for complimentary line-ups.
		    // Note: we already know both diagonals do not match.
		    if ( (s0 == s4) && (s2 == s4) ) {
			// There is a complimentary line-up in 0-1-2.
			this.winnerScore = this.score(4,7,1,0,2);
		    } else if ( (s3 == s4) && (s5 == s4) ) {
			// There is a complimentary line-up in 3-4-5.
			this.winnerScore = this.score(1,7,4,3,5);
		    } else if ( (s6 == s4) && (s8 == s4) ) {
			// There is a complimentary line-up in 6-7-8.
			this.winnerScore = this.score(1,4,7,6,8);
		    } else {
			this.winnerScore = Qt3Game.Score.FULL_POINT;
		    }
		}
		return;
	    }
	   
	    if ( (s3 == s4) && (s5 == s4) ) {
		// There is a line-up in 3-4-5. 
		this.gameOver = true;
		// Check for competing line-ups.
		if ( (s0 != null) && (s0 != s4) && (s1 == s0) && (s2 == s0) ) {
		    // There is a competing line-up in 0-1-2.
		    this.winner = this.scoreCompetingLineups(3,4,5, 0,1,2);
		    this.winnerScore = Qt3Game.Score.FULL_POINT;
		    this.loserScore = Qt3Game.Score.HALF_POINT;
		} else if ( (s6 != null) && (s6 != s4)
		       	&& (s7 == s2) && (s8 == s2) ) {
		    // There is a competing line-up in 6-7-8.
		    this.winner = this.scoreCompetingLineups(3,4,5, 6,7,8);
		    this.winnerScore = Qt3Game.Score.FULL_POINT;
		    this.loserScore = Qt3Game.Score.HALF_POINT;
		} else {
		    this.winner = s4;
		    // Check for complimentary line-ups.
		    // Note: we already know both diagonals and 1-4-7 do
		    // not match.
		    if ( (s0 == s4) && (s6 == s4) ) {
			// There is a complimentary line-up in 0-3-6.
			this.winnerScore = this.score(5,4,3,0,6);
		    } else if ( (s2 == s4) && (s8 == s4) ) {
			// There is a complimentary line-up in 2-5-8.
			this.winnerScore = this.score(3,4,5,2,8);
		    } else {
			this.winnerScore = Qt3Game.Score.FULL_POINT;
		    }
		}
		return;
	    }
	   
	    // Although s4 is owned, there are no line-ups.  Check for
	    // other line-ups.
	}
       
	if ( s0 != null ) {
	    if ( (s1 == s0) && (s2 == s0) ) {
		// A line-up in 0-1-2.
		this.gameOver = true;

		// Note: at this point we know there are no line-ups
		// using s4. 
		// Check for competing line-ups.
		if ( (s6 != null) && (s6 != s0) && (s7 == s6) && (s8 == s6) ) {
		    // A competing line-up in 6-7-8.
		    this.winner = this.scoreCompetingLineups(0,1,2, 6,7,8);
		    this.winnerScore = Qt3Game.Score.FULL_POINT;
		    this.loserScore = Qt3Game.Score.HALF_POINT;
		} else {
		    this.winner = s0;
		    // Check for complimentary line-ups.
		    if ( (s3 == s0) && (s6 == s0) ) {
			this.winnerScore = this.score(2,1,0,3,6);
		    } else if ( (s5 == s0) && (s8 == s0) ) {
			this.winnerScore = this.score(0,1,2,5,8);
		    } else {
			this.winnerScore = Qt3Game.Score.FULL_POINT;
		    }
		}
		return;
	    }
	   
	    if ( (s3 == s0) && (s6 == s0) ) {
		// A line-up in 0-3-6.
		this.gameOver = true;

		// Note: at this point we know there are no line-ups
		// using s4 and there is no line-up in 0-1-2. 
		// Check for competing line-ups.
		if ( (s2 != null) && (s2 != s0) && (s5 == s2) && (s8 == s2) ) {
		    // A competing line-up in 2-5-8.
		    this.winner = this.scoreCompetingLineups(0,3,6, 2,5,8);
		    this.winnerScore = Qt3Game.Score.FULL_POINT;
		    this.loserScore = Qt3Game.Score.HALF_POINT;
		} else {
		    this.winner = s0;
		    // Check for complimentary line-ups.
		    if ( (s7 == s0) && (s8 == s0) ) {
			this.winnerScore = this.score(0,3,6,7,8);
		    } else {
			this.winnerScore = Qt3Game.Score.FULL_POINT;
		    }
		}
		return;
	    }

	    // Although s0 is owned, there are no line-ups.  Check for
	    // other line-ups.
	}
       
	if ( s8 != null ) {
	    if ( (s2 == s8) && (s5 == s8) ) {
		// A line-up in 2-5-8.
		this.gameOver = true;

		// Note: at this point we know there are no line-ups
		// using s4 or s0. 
		// Therefore there can be no competing line-ups.

		this.winner = s8;
		// Check for complimentary line-ups.
		if ( (s6 == s8) && (s7 == s8) ) {
		    this.winnerScore = this.score(2,5,8,7,6);
		} else {
		    this.winnerScore = Qt3Game.Score.FULL_POINT;
		}
		return;
	    }
	   
	    if ( (s6 == s8) && (s7 == s8) ) {
		// A line-up in 6-7-8.
		this.gameOver = true;

		// Note: at this point we know there are no line-ups
		// using s4 or s0 and no line-up in 2-5-8. 
		// Therefore there can be no competing or complimentary
		// line-ups.

		this.winner = s8;
		this.winnerScore = Qt3Game.Score.FULL_POINT;
		return;
	    }

	    // Although s8 is owned, there are no line-ups.
	}

        if (       (s0 != null) // check to see if all squares are filled
                && (s1 != null)
                && (s2 != null)
                && (s3 != null)
                && (s4 != null)
                && (s5 != null)
                && (s6 != null)
                && (s7 != null)
                && (s8 != null) ) {
            this.gameOver = true;
            this.winner = null; // "cat's game"
            this.winnerScore = Qt3Game.Score.NO_POINTS;
            this.loserScore = Qt3Game.Score.NO_POINTS;
            return;
        }

    } // evaluateForEndOfGame()

   /*-------------------------------------------------------------------*
    *	METHOD:		score()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Tue Jan 11 11:31:43 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the score for a complimentary line-up.
    */

    private Score score(
	int	firstLineupMemberA,	// squareIndex of member of 1st line-up
	int	firstLineupMemberB,	// squareIndex of member of 1st line-up
	int	sharedLineupMember,	// squareIndex in both line-ups
	int	secondLineupMemberA,	// squareIndex of member of 2nd line-up
	int	secondLineupMemberB	// squareIndex of member of 2nd line-up
	)
    {
	final Qt3Square[] board = this.boardState;
	final int sharedSquareMove = board[sharedLineupMember].getOwningMove();

	if (   (board[firstLineupMemberA].getOwningMove() < sharedSquareMove)
	    && (board[firstLineupMemberB].getOwningMove() < sharedSquareMove)
	    && (board[secondLineupMemberA].getOwningMove() < sharedSquareMove)
	    && (board[secondLineupMemberB].getOwningMove() < sharedSquareMove)
	    ) {
	    return Qt3Game.Score.DOUBLE_POINTS;
	}

	return Qt3Game.Score.POINT_AND_A_HALF;
    } // score()

   /*-------------------------------------------------------------------*
    *	METHOD:		scoreCompetingLineups()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Tue Jan 11 11:32:21 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the winner of competing line-ups.
    */

    private Qt3Player scoreCompetingLineups(
	int	firstLineupMemberA,	// squareIndex of member of 1st line-up
	int	firstLineupMemberB,	// squareIndex of member of 1st line-up
	int	firstLineupMemberC,	// squareIndex of member of 1st line-up
	int	secondLineupMemberA,	// squareIndex of member of 2nd line-up
	int	secondLineupMemberB,	// squareIndex of member of 2nd line-up
	int	secondLineupMemberC	// squareIndex of member of 2nd line-up
	)
    {
	final Qt3Square[] board = this.boardState;

	//
	// Determine the move that completed the first line-up.
	//

	int	firstLineupMove	= board[firstLineupMemberA].getOwningMove();
	int	tmpMove;

	tmpMove = board[firstLineupMemberB].getOwningMove();
	if ( tmpMove > firstLineupMove ) {
	    firstLineupMove = tmpMove;
	}

	tmpMove = board[firstLineupMemberC].getOwningMove();
	if ( tmpMove > firstLineupMove ) {
	    firstLineupMove = tmpMove;
	}

	//
	// Determine whether second line-up was completed before or after
	// the first line-up.
	//

	if (   (board[secondLineupMemberA].getOwningMove() > firstLineupMove)
	    || (board[secondLineupMemberB].getOwningMove() > firstLineupMove)
	    || (board[secondLineupMemberC].getOwningMove() > firstLineupMove)
	   ) {
	    return board[firstLineupMemberA].getOwner();
	}

	return board[secondLineupMemberA].getOwner();

    } // scoreCompetingLineups()

   /*-------------------------------------------------------------------*
    *	METHOD:		isGameOver()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 21:51:37 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns whether this step concludes the game.
    */

    public boolean isGameOver()
    {
	return this.gameOver;
    } // isGameOver()

   /*-------------------------------------------------------------------*
    *	METHOD:		getWinner()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 21:53:22 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the winner of the game as decided by this step.
    * If "X" won, returns the constant 'Qt3Player.X'.
    * If "O" won, returns the constant 'Qt3Player.O'.
    * If the game is a "Cat's Game", returns null.
    * @throws IllegalStateException if the game is not over.
    */

    public final Qt3Player getWinner()
	throws IllegalStateException
    {
	if ( ! this.gameOver ) {
	    throw new IllegalStateException();
	}

	return this.winner;
    } // getWinner()

   /*-------------------------------------------------------------------*
    *	METHOD:		getWinnerScore()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Wed Feb  9 11:13:04 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the score of the winner.
    * @throws IllegalStateException if the game is not over.
    */

    public Score getWinnerScore()
	throws IllegalStateException
    {
	if ( ! this.gameOver ) {
	    throw new IllegalStateException();
	}

	return this.winnerScore;
    } // getWinnerScore()

   /*-------------------------------------------------------------------*
    *	METHOD:		getLoserScore()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Wed Feb  9 11:15:33 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the score of the loser.
    * @throws IllegalStateException if the game is not over.
    */

    public Score getLoserScore()
	throws IllegalStateException
    {
	if ( ! this.gameOver ) {
	    throw new IllegalStateException();
	}

	return this.loserScore;
    } // getLoserScore()

   /*-------------------------------------------------------------------*
    *	METHOD:		hasSelfRefEntanglement()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan 10 20:55:52 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns whether the state consists of a collapsible, self-referential
    * entanglement.
    */

    /*package*/ boolean hasSelfRefEntanglement()
    {
	final Qt3Tangle tangle = this.moveTangle;
	return ((tangle != null) && tangle.isCollapsible());
    } // hasSelfRefEntanglement()

   /*-------------------------------------------------------------------*
    *	METHOD:		getTangle()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan 10 21:05:18 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the tangle made by the previous move, if any.
    */

    /*package*/ Qt3Tangle getTangle()
    {
	return this.moveTangle;
    } // getTangle()

   /*-------------------------------------------------------------------*
    *	METHOD:		getBoardSquares()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Wed Feb  2 22:54:55 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the set of Quantum Tic Tac Toe squares that describe this
    * state of the game.
    */

    /*package*/ Qt3Square[] getBoardSquares()
    {
	return this.boardState;
    } // getBoardSquares()

   /*-------------------------------------------------------------------*
    *	METHOD:		getClassicalEnsemble()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Mon Jan  3 22:00:49 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns the set of classical Tic Tac Toe games currently represented
    * by the state of the Quantum Tic Tac Toe game.
    */

    public Qt3ClassicalBoardImpl[] getClassicalEnsemble()
    {
	return this.equivalentClassicalGames;
    } // getClassicalEnsemble()

   /*-------------------------------------------------------------------*
    *	METHOD:		boardStateContradicts()
    *	AUTHOR:		Duane Gibson
    *	CREATION DATE:	Tue Feb  1 22:35:52 PST 2005
    *-------------------------------------------------------------------*/
   /**
    * Returns <code>true</code> if the current board state contradicts
    * the given classical Tic Tac Toe board.
    *
    * @param classicBoard	classical board. 
    */

    private boolean boardStateContradicts(
	final Qt3ClassicalBoardImpl classicBoard
	)
    {
	final Qt3Square[] board = this.boardState;

	StringBuffer logBuf = null;
	if ( log.isLoggable(Level.INFO) ) {
	    logBuf = new StringBuffer();
	    logBuf.append("Checking classical board for contradictions:");
	}

	for ( int index = 0 ; index < Qt3Constants.MAX_SQUARES ; ++index ) {
	    final Qt3Player classicOwner =
				classicBoard.getPlayerAtSquareIndex(index);

	    if ( logBuf != null ) {
		if ( (index % 3) == 0 ) {
		    logBuf.append("\n     ");
		} else {
		    logBuf.append("| ");
		}
		if ( classicOwner == null ) {
		    logBuf.append('-');
		} else {
		    logBuf.append(classicOwner);
		}
		logBuf.append(' ');
	    }

	    if ( classicOwner != null ) {
		Qt3Square  square = board[index];

		// Classical boards are built using real and pending info.
		Qt3Player realOwner = square.getOwner();
		if ( realOwner == null ) {
		    realOwner = square.getPendingOwner();
		}


		if ( realOwner != null ) {
		    boolean contradicts = false;

		    if ( classicOwner != realOwner ) {
			contradicts = true;
		    } else if ( classicBoard.getMoveAtSquareIndex(index)
			    != square.getPendingOwningMove() ) {
			contradicts = true;
		    }

		    if ( contradicts ) {

			if ( logBuf != null ) {
			    logBuf.append("<-- CONTRADICTION");
			    log.log(Level.INFO, logBuf.toString());
			}

			return true;
		    }
		}
	    }
	}

	if ( logBuf != null ) {
	    logBuf.append("-- no contradictions found.");
	    log.log(Level.INFO, logBuf.toString());
	}

	return false;

    } // boardStateContradicts()

} // class Qt3State

